﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Windows.Forms;

namespace Downloader
{
    class LogFile
    {
        StreamWriter m_file;
        Object o;
        public LogFile()
        {
            o = new Object();
            m_file = new StreamWriter("C:\\Users\\ding\\Desktop\\log.file");
        }
        public void WriteLine(String s)
        {
            lock(o)
            {
                m_file.WriteLine(s);
                m_file.Flush();
            }
        }

    }
    class  DownloadEventArgv
    {
        long _id;
        long _downloadedsize;
        long _totalsize;
        string msg;
        public DownloadEventArgv()
        {
        }
        public DownloadEventArgv(long id,long downloadedsize,long ts = -1)
        {
            _id = id;
            _downloadedsize = downloadedsize;
            if (ts != -1)
            {
                _totalsize = ts;
            }
        }
        public DownloadEventArgv(long id, string msg)
        {
            _id = id;
            this.msg = msg;
        }
        public long downloadedSize
        {
            get { return _downloadedsize; }
        }
        public long totalSize
        {
            get { return _totalsize; }
        }
        public long id
        {
            get { return _id; }
        }
    }
    delegate void DownloadMessageHandle(object sender,DownloadEventArgv argv);

    class Download
    {
    /************************************************************************/
    /*                      成员变量                                        */
    /************************************************************************/
        internal const long UNSTART = 0;
        internal const long RUNNING = 1;
        internal const long SUSPEND = 2;
        internal const long COMPLETED = 3;
        internal const long EXCEPT = 4;
        

        internal long m_id;
        internal string m_url;
        internal string m_urldomain;
        internal string m_urlpath;
        internal string m_localpath;
        internal string m_localfilename;
        internal string m_localfullfilename;
        internal string m_configfilename;
        internal ConfigFile  m_config;
        internal const string cofig_sufix = ".dlcfg";

        internal bool m_bRangable;
        internal bool m_bStoped;
        internal long m_status;

        internal long m_filelength;
        internal long m_blocksize;
        internal long m_downloadedProgress;
        internal long m_downloadProgress;
        internal int m_downloadThreadCount;
        internal int m_downloadedSize;
        internal long m_maxblock;
        private Semaphore m_DownTask;
        private Semaphore m_DownloadThread;
        private Semaphore m_DiskCacheFull;
        private AutoResetEvent m_DiskCacheIntoOne;
        private Semaphore m_DiskCacheEmpty;
        private Object m_DiskCacheOperate;
        private List<Thread> m_downThreads;
        private Dictionary<long, byte[]> m_DiskCache;
        private Thread m_DiskWriteThread;
        private Thread m_MainThread;
        private StreamWriter m_localfile;

        //public LogFile m_log;

        //各种事件
        public event DownloadMessageHandle evt_SliceDone;
        public event DownloadMessageHandle evt_Start;
        public event DownloadMessageHandle evt_Completed;
        public event DownloadMessageHandle evt_Suspend;
        public event DownloadMessageHandle evt_Except;
/************************************************************************/
/*                        构造函数                                      */
/************************************************************************/
        public Download(long id,string localpath, string filename, string url=null, int thread_num = 5, int blocksize = 102400)
        {
            m_id = id;
            m_bStoped = false;
            m_url = url;
            m_localpath = localpath;
            m_localfilename = filename;
            m_localfullfilename = localpath + '\\' + filename;
            m_downloadThreadCount = thread_num;
            //空表示新下载，非空表示继续下载
            m_configfilename = m_localfullfilename + cofig_sufix;
            m_blocksize = blocksize;
            m_status = UNSTART;

            //m_log = new LogFile();
        }

        /************************************************************************/
        /*                          外部方法                                    */
        /************************************************************************/
        public void Start()
        {
            Thread thread = new Thread(new ThreadStart(_Start));
            thread.Start();
        }
        public void _Start()
        {
            if(!PreDownload())
            {
                this.m_status = EXCEPT;
                DownloadEventArgv argv1 = new DownloadEventArgv(this.m_id,"error");
                if (evt_Except != null) evt_Except(this, argv1);
                return;
            }
            //可断点
            if (this.m_bRangable)
            {
                m_MainThread = new Thread(new ThreadStart(MultiDownMainProc));
                
            }//不可断点
            else
            {
                m_MainThread = new Thread(new ThreadStart(SingalDownMainProc));
            }
            m_status = RUNNING;
            m_bStoped = false;
            DownloadEventArgv argv = new DownloadEventArgv(this.m_id, (this.m_downloadedProgress + 1) * this.m_blocksize, this.m_filelength);
            if (evt_Start != null) evt_Start(this, argv);
            m_MainThread.Start();
        }

        public void Suspend()
        {
            this.m_bStoped = true;
            m_status = SUSPEND;
        }

        /************************************************************************/
        /*                      内部私有方法                                    */
        /************************************************************************/
        //连接服务器，获取下载信息
        private bool PreDownload()
        {
            //读取配置文件，如果是找不到，则认为是新下载，并初始化各项参数
            bool success;
            m_config = new ConfigFile(m_configfilename, this,out success);
            if (!success) return false;
            m_downThreads = new List<Thread>();
            return true;
        }
        
        //创建删除下载配置文件
        private bool CompleteDownload()
        {
            File.Delete(m_configfilename);
            DownloadEventArgv argv = new DownloadEventArgv(this.m_id,this.m_filelength);
            if (evt_Completed != null) evt_Completed(this, argv);
            return true;
        }
        /************************************************************************/
        /*                      单线程下载                                      */
        /************************************************************************/

        private void SingalDownMainProc()
        {
            m_localfile = new StreamWriter(new FileStream(m_localfullfilename, FileMode.Append));
            HttpDown httpdown =new HttpDown(m_url);
            m_downloadedSize = 0;
            while(true)
            {
                if (m_status != RUNNING)
                {
                    break;
                }
                int readed;
                byte[] buffer = httpdown.ReadBytes((int)m_blocksize,out readed);
                if (buffer == null)
                {
                    this.m_status = COMPLETED;
                    break;
                }
                m_downloadedSize += readed;
                m_localfile.BaseStream.Write(buffer,0,readed);
                m_filelength = m_downloadedSize;
                DownloadEventArgv argv = new DownloadEventArgv(this.m_id, m_downloadedSize,m_downloadedSize);
                if (evt_SliceDone != null) evt_SliceDone(this, argv);
            }
            if (this.m_status==COMPLETED)
            {
                CompleteDownload();
                DownloadEventArgv argv = new DownloadEventArgv(this.m_id,this.m_filelength);
                if (evt_Completed != null) evt_Completed(this, argv);
            } 
            else
            {
                m_status = UNSTART;
            }

        }

        /************************************************************************/
        /*                      多线程下载                                   */
        /************************************************************************/

        private void MultiDownMainProc()
        {

            m_DownTask = new Semaphore(1, 1);
            m_DiskCacheOperate = new Object();
            m_DownloadThread = new Semaphore(0, m_downloadThreadCount);
            m_DiskCacheEmpty = new Semaphore(m_downloadThreadCount, m_downloadThreadCount);
            m_DiskCacheFull = new Semaphore(0, m_downloadThreadCount);
            m_DiskCacheIntoOne = new AutoResetEvent(false);

            m_localfile = new StreamWriter(new FileStream(m_localfullfilename, FileMode.Append));
            m_DiskCache = new Dictionary<long, byte[]>();
            //开始下载线程
            for (int i = 0; i < m_downloadThreadCount; ++i)
            {
                Thread thread = new Thread(new ThreadStart(DownloadThread));
                //thread.IsBackground = false;
                thread.Start();
                m_downThreads.Add(thread);
            }
            //开始写入线程
            //WaitHandle[] l = new WaitHandle[1] { new AutoResetEvent(false) };
            AutoResetEvent diskthread_complete= new AutoResetEvent(false);
            m_DiskWriteThread = new Thread(new ParameterizedThreadStart(WriteDiskThread));
            m_DiskWriteThread.Start(diskthread_complete);

            //等待下载线程结束
            int num = 0;
            while (m_DownloadThread.WaitOne())
            {
                num++;
                if (num == m_downloadThreadCount)
                {
                    break;
                }
            }
            //恢复写入线程
            m_DiskCacheIntoOne.Set();
            diskthread_complete.WaitOne();
            if(this.m_status == SUSPEND)
            {
                DownloadEventArgv argv = new DownloadEventArgv(this.m_id, this.m_downloadedProgress * m_blocksize);
                if (evt_Suspend != null) evt_Suspend(this, argv);
            }else if(this.m_status == COMPLETED)
            {
                CompleteDownload();
            }
            //等待写入线程结束
        }

        //获取一个下载任务
        private long getNewTask()
        {
            long id;
            m_DownTask.WaitOne();
            id = m_downloadProgress;
            m_downloadProgress += 1;
            m_DownTask.Release();
            return id;
        }
        //下载线程
        private void DownloadThread()
        {
            while (true)
            {
                if(m_bStoped)
                {
                    break;
                }
                m_DiskCacheEmpty.WaitOne();
                long id = getNewTask();
                long a = id * m_blocksize;
                long b = (id+1) * m_blocksize-1;

                if (a > m_filelength-1)
                    break;
                if (b > m_filelength-1)
                {
                    b = m_filelength - 1;
                }

                byte[] r = null;
                if (m_url.StartsWith("http://"))
                {
                    r = HttpUtil.getHttpFileSlice(m_url, a, b);
                    if (r==null)
                    {
                        DownloadEventArgv argv = new DownloadEventArgv(this.m_id, this.m_downloadedProgress * m_blocksize);
                        if (evt_Except != null) evt_Except(this, argv);
                        this.m_status = EXCEPT;
                        break;
                    }
                }
                else if (m_url.StartsWith("ftp://"))
                {
                    r = FtpUtil.getFtpFileSlice(m_url, a, b);
                }
                

                lock(m_DiskCacheOperate)
                {
                    m_DiskCache.Add(id, r);
                }
                m_DiskCacheIntoOne.Set();
            }
            m_DownloadThread.Release();
        }
        //磁盘写入线程
        private void WriteDiskThread(Object evt)
        {
            AutoResetEvent e = (AutoResetEvent)evt;
            while (m_DiskCacheIntoOne.WaitOne())
            {
                if (m_bStoped)
                {
                    break;
                }
                int num = 0;
                while(true)
                {
                    bool incache;
                    lock(m_DiskCacheOperate)
                    {
                        incache = m_DiskCache.ContainsKey(m_downloadedProgress + 1);
                    }

                    if (incache)
                    {
                        num++;
                        m_downloadedProgress += 1;
                        byte[] buffer;
                        lock (m_DiskCacheOperate)
                        {
                            buffer = m_DiskCache[m_downloadedProgress];
                        }
                        m_localfile.BaseStream.Write(buffer, 0, buffer.Length);

                        lock (m_DiskCacheOperate)
                        {
                            m_DiskCache.Remove(m_downloadedProgress);
                        }

                        m_config.WriteConfig(this);

                        DownloadEventArgv argv = new DownloadEventArgv(this.m_id, this.m_downloadedProgress * this.m_blocksize);
                        if (evt_SliceDone != null) evt_SliceDone(this, argv);
                    }
                    else
                    {
                        break;
                    }
                  
                }
                for (int i = 0; i < num;++ i)
                {
                    m_DiskCacheEmpty.Release();
                }

                if (m_downloadedProgress >= m_maxblock)
                {
                    m_status = COMPLETED;
                    break;
                }
            }
            m_localfile.Close();
            e.Set();
        }
    }
}
